<?php
require_once 'vendor/autoload.php';

/**
 * ETF定投模型类【用于分析ETF】
 * 实现基于价格区间的ETF定期投资策略，包含买入、卖出和盈利计算功能
 */
class ETFInvestment
{
    // 最高买入价格阈值
    private $maxPrice;
    // 最低买入价格阈值  
    private $minPrice;
    // 基础投资金额
    private $baseAmount;
    // 要处理的Excel文件路径
    private $filePath;
    // 输出文件路径
    private $outputPath;
    // 投资规则数组，根据价格区间设置不同投资金额
    private $investmentRules = [];
    // 当前投资轮次
    private $currentRound = 1;
    // 累计盈利金额
    private $totalProfit = 0;
    // 剩余未卖出份额
    private $remainingShares = 0;
    // 剩余股份的成本价
    private $remainingCost = 0;
    // 所有轮次的投资数据记录
    private $roundsData = [];

    private $spreadsheet; // PhpSpreadsheet实例
    private $sheet; // 当前活动的Sheet实例

    /**
     * 构造函数
     * @param float $maxPrice 最高买入价格
     * @param float $minPrice 最低买入价格
     * @param float $baseAmount 基础投资金额
     * @param string $filePath 要处理的Excel文件路径
     */
    public function __construct($filePath,$maxPrice, $minPrice, $baseAmount)
    {
        $this->maxPrice = $maxPrice;
        $this->minPrice = $minPrice;
        $this->baseAmount = $baseAmount;
        $this->filePath = $filePath;
        $this->outputPath = 'out/output_'.$filePath;
        $this->setupInvestmentRules();
    }

    /**
     * 设置投资规则
     * 将价格区间分为4档，每档投资金额为基础金额的2^(4-n)倍
     */
    private function setupInvestmentRules()
    {
        $priceRange = $this->maxPrice - $this->minPrice;
        $step = $priceRange / 4;
        $currentMax = $this->maxPrice;

        for ($i = 4; $i > 0; $i--) {
            $currentMin = $currentMax - $step;
            $amount = $this->baseAmount * pow(2, (4 - $i));
            $this->investmentRules[] = [
                'min' => $currentMin,
                'max' => $currentMax,
                'amount' => $amount
            ];
            echo "区间{$i}: 价格范围: ". number_format($currentMin, 2). "元 - ". number_format($currentMax, 2). "元, 投资金额: ". number_format($amount, 2). "元\n";
            $currentMax = $currentMin;
        }
    }

    /**
     * 处理Excel文件
     * 读取Excel中的日期和收盘价数据，执行定投和卖出逻辑
     */
    public function processExcelFile()
    {
        try {
            $spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load('xlsx/' . $this->filePath);
            $worksheet = $spreadsheet->getActiveSheet();
            $highestRow = $worksheet->getHighestRow();

            $lowestPrice = PHP_FLOAT_MAX;
            $buyRecords = [];
            $totalInvestment = 0;
            $totalShares = 0;
            $carryOverValue = 0;
            $lastRemainingShares = 0;

            for ($row = 2; $row <= $highestRow; $row++) {
                $dateValue = $worksheet->getCell('A' . $row)->getValue();
                $date = is_numeric($dateValue)
                    ? \DateTime::createFromFormat('U', ($dateValue - 25569) * 86400)->format('Y-m-d')
                    : $dateValue;

                $closePrice = (float)$worksheet->getCell('B' . $row)->getValue();

                if ($closePrice < $lowestPrice) {
                    $lowestPrice = $closePrice;
                }

                if ($closePrice >= $lowestPrice * 1.15) {
                    $this->processSellRound($buyRecords, $closePrice, $lowestPrice, $date, $lastRemainingShares, $carryOverValue);
                    $lowestPrice = PHP_FLOAT_MAX;
                    $buyRecords = [];
                    $totalInvestment = 0;
                    $totalShares = 0;
                    $this->currentRound++;
                }

                if ($row == 2 || ($row - 2) % 5 == 0) {
                    $this->processWeeklyInvestment($closePrice, $date, $buyRecords, $totalInvestment, $totalShares);
                }
            }

            $this->printSummary($buyRecords);
        } catch (Exception $e) {
            echo "读取Excel文件出错: " . $e->getMessage();
        }
    }

    /**
     * 处理每周定投
     * @param float $price 当前价格
     * @param string $date 当前日期
     * @param array &$buyRecords 买入记录数组(引用)
     * @param float &$totalInvestment 总投资金额(引用)
     * @param float &$totalShares 总份额(引用)
     */
    private function processWeeklyInvestment($price, $date, &$buyRecords, &$totalInvestment, &$totalShares)
    {
        $amount = 0;
        foreach ($this->investmentRules as $rule) {
            if ($price >= $rule['min'] && $price <= $rule['max']) {
                $amount = $rule['amount'];
                break;
            }
        }

        if ($amount > 0) {
            $shares = $amount / $price;
            $totalInvestment += $amount;
            $totalShares += $shares;
            $buyRecords[] = [
                'date' => $date,
                'price' => $price,
                'amount' => $amount,
                'shares' => $shares
            ];
            // 添加每周定投明细输出
            echo "日期: {$date} ";
            echo "价格: " . number_format($price, 4) . "元 ";
            echo "定投金额: " . number_format($amount, 2) . "元 ";
            echo "买入份额: " . number_format($shares, 4) . "股 ";
            echo "累计投资: " . number_format($totalInvestment, 2) . "元 ";
            echo "累计份额: " . number_format($totalShares, 4) . "股\n ";
            
        }
    }

    /**
     * 处理卖出轮次
     * @param array $buyRecords 买入记录
     * @param float $sellPrice 卖出价格
     * @param float $lowestPrice 本轮最低价
     * @param string $endDate 结束日期
     * @param float &$lastRemainingShares 上轮剩余份额(引用)
     * @param float &$carryOverValue 上轮剩余金额(引用)
     */
    private function processSellRound($buyRecords, $sellPrice, $lowestPrice, $endDate, &$lastRemainingShares, &$carryOverValue)
    {
        if (empty($buyRecords)) return;

        $roundData = $this->calculateRoundData($buyRecords, $endDate,$sellPrice, $lastRemainingShares, $carryOverValue, $lowestPrice);
        $this->updateRemainingValues($roundData, $lastRemainingShares, $carryOverValue);
        $this->storeRoundData($roundData, $buyRecords[0]['date'], $endDate, $lowestPrice, $sellPrice);
        $this->printRoundResult($roundData, $lowestPrice, $sellPrice);
    }

    /**
     * 计算轮次数据
     * @param array $buyRecords 买入记录
     * @param float $sellPrice 卖出价格
     * @param float $lastRemainingShares 上轮剩余份额
     * @param float $carryOverValue 上轮剩余金额
     * @param float $lowestPrice 本轮最低价
     * @return array 包含本轮各项数据的数组
     */
    private function calculateRoundData($buyRecords, $endDate,$sellPrice, $lastRemainingShares, $carryOverValue, $lowestPrice)
    {
        $roundData = [
            'round_shares' => 0,
            'round_investment' => 0,
            'total_round_investment' => 0,
            'all_round_shares' => 0,
            'start_date' => $buyRecords[0]['date'],  // 新增开始时间
            'end_date' => $endDate,
            'sell_price' => $sellPrice,
            'lowest_price' => $lowestPrice,
        ];

        foreach ($buyRecords as $record) {
            $roundData['total_round_investment'] += $record['amount'];
            if ($record['price'] < $sellPrice) {
                $roundData['round_shares'] += $record['shares'];
                $roundData['round_investment'] += $record['amount'];
            }
            $roundData['all_round_shares'] += $record['shares'];
        }

        $roundData['total_sell_amount'] = $roundData['round_shares'] * $sellPrice;
        $roundData['remaining_shares'] = $lastRemainingShares + $roundData['all_round_shares'] - $roundData['round_shares'];
        $roundData['remaining_value'] = $roundData['total_round_investment'] + $carryOverValue - $roundData['round_investment'];
        $roundData['round_profit'] = $roundData['total_sell_amount'] - $roundData['round_investment'];
        $roundData['remaining_cost'] = $this->calculateRemainingCost($buyRecords, $sellPrice, $carryOverValue, $roundData['remaining_shares']);
        $roundData['total_profit'] = ($roundData['remaining_shares'] * $sellPrice - $roundData['remaining_value']) + $this->totalProfit;
        // 检查 lowestPrice 是否为 0，避免出现除以 0 导致结果为 inf 的情况
        if ($lowestPrice == 0) {
            $roundData['profit_rate'] = 0;
        } else {
            $roundData['profit_rate'] = ($sellPrice / $lowestPrice - 1) * 100;
        }
        $roundData['remaining_rate'] = $roundData['remaining_value']>0 ? 100 * ($roundData['remaining_shares'] * $sellPrice - $roundData['remaining_value']) / $roundData['remaining_value'] :0; // 剩余部分的盈利比例
        return $roundData;
    }

    /**
     * 计算剩余股份成本
     * @param array $buyRecords 买入记录
     * @param float $sellPrice 卖出价格
     * @param float $remainingShares 剩余份额
     * @param float $carryOverValue 上一轮剩余金额
     * @return float 剩余股份的成本价
     */
    private function calculateRemainingCost($buyRecords, $sellPrice, $carryOverValue,$remainingShares)
    {
        if ($remainingShares <= 0) return 0;

        $remainingCost = 0;
        $count = 0;
        foreach ($buyRecords as $record) {
            if ($record['price'] >= $sellPrice) {
                $remainingCost += $record['amount'];
                $count++;
            }
        }
        return $remainingShares > 0 ? ($remainingCost + $carryOverValue) / $remainingShares : 0;
    }

    /**
     * 更新剩余值
     * @param array $roundData 本轮数据
     * @param float &$lastRemainingShares 上轮剩余份额(引用)
     * @param float &$carryOverValue 上轮剩余金额(引用)
     */
    private function updateRemainingValues($roundData, &$lastRemainingShares, &$carryOverValue)
    {
        $lastRemainingShares = $roundData['remaining_shares'];
        $carryOverValue = $roundData['remaining_value'];
        $this->remainingShares = $roundData['remaining_shares'];
        $this->remainingCost = $roundData['remaining_cost'];
        $this->totalProfit += $roundData['round_profit'];
    }

    /**
     * 存储轮次数据
     * @param array $roundData 本轮数据
     * @param string $startDate 开始日期
     * @param string $endDate 结束日期
     * @param float $lowestPrice 最低价
     * @param float $sellPrice 卖出价
     */
    private function storeRoundData($roundData, $startDate, $endDate, $lowestPrice, $sellPrice)
    {
        $this->roundsData[] = [
            'round' => $this->currentRound,
            'start_date' => $startDate,
            'end_date' => $endDate,
            'sell_amount' => $roundData['total_sell_amount'],
            'total_investment' => $roundData['total_round_investment'],
            'total_shares' => $roundData['all_round_shares'],
            'profit' => $roundData['round_profit'],
            'sell_price' => $sellPrice,
            'lowest_price' => $lowestPrice,
            'total_profit' => $roundData['total_profit'],
            'remaining_value' => $roundData['remaining_value'],
            'remaining_shares' => $roundData['remaining_shares'],
            'remaining_cost' => $roundData['remaining_cost'],
            'profit_rate' => $roundData['profit_rate']
        ];
    }

    /**
     * 打印轮次结果
     * @param array $roundData 本轮数据
     * @param float $lowestPrice 最低价
     * @param float $sellPrice 卖出价
     */
    private function printRoundResult($roundData, $lowestPrice, $sellPrice)
    {
        echo "\n第{$this->currentRound}轮卖出信号:时间 {$roundData['start_date']} 至 {$roundData['end_date']}\n";
        echo "最低价: " . number_format($lowestPrice, 2) . "元\n";
        echo "卖出价: " . number_format($sellPrice, 2) . "元\n";
        echo "区间所有定投: " . number_format($roundData['total_round_investment'], 2) . "元\n";
        echo "区间所有买入份额: " . number_format($roundData['all_round_shares'], 2) . "股\n";
        echo "区间买入金额: " . number_format($roundData['round_investment'], 2) . "元\n";
        echo "区间卖出金额: " . number_format($roundData['total_sell_amount'], 2) . "元\n";
        echo "区间卖出份额: " . number_format($roundData['round_shares'], 2) . "股\n";
        echo "区间盈利: " . number_format($roundData['round_profit'], 2) . "元\n";
        echo "剩余金额: " . number_format($roundData['remaining_value'], 2) . "元\n";
        echo "剩余份额: " . number_format($roundData['remaining_shares'], 2) . "股\n";
        echo "成本价:" . number_format($roundData['remaining_cost'], 2) . "元\n";
        echo "累计盈利: " . number_format($roundData['total_profit'], 2) . "元\n";
        echo "剩余盈利比例: " . number_format($roundData['remaining_rate'], 2) . "%\n";
        echo "涨幅: " . number_format($roundData['profit_rate'], 2) . "%\n\n";
    }

    /**
     * 打印汇总结果
     * 输出所有轮次的投资数据汇总
     */
    private function printSummary($buyRecords)
    {
        // echo "\n所有卖出轮次详细数据:\n";
        // foreach ($this->roundsData as $data) {
        //     echo "第{$data['round']}轮,";
        //     echo "时间 {$data['start_date']} 至 {$data['end_date']},";
        //     echo "卖出金额: " . number_format($data['sell_amount'], 2) . "元,";
        //     echo "总投资: " . number_format($data['total_investment'], 2) . "元,";
        //     echo "总份额: " . number_format($data['total_shares'], 4) . "股,";
        //     echo "此轮盈利: " . number_format($data['profit'], 2) . "元,";
        //     echo "累计盈利: " . number_format($data['total_profit'], 2) . "元\n";
        // }         
        // 计算buyRecords中的投资金额和份额
        $buyRecordsInvestment = 0;
        $buyRecordsShares = 0;
        $lastPrice = 0;
        foreach ($buyRecords as $record) {
            $buyRecordsInvestment += $record['amount'];
            $buyRecordsShares += $record['shares'];
            $lastPrice = $record['price'];
        }   
        $remainingCost = $this->remainingCost * $this->remainingShares + $buyRecordsInvestment;// 计算剩余成本
        $total_profit = ($this->remainingShares+$buyRecordsShares)* $lastPrice - $this->remainingCost * $this->remainingShares - $buyRecordsInvestment + $this->totalProfit; // 计算总盈利 
        // 新增最终资产和收益率输出
        echo "\n最终资产情况:\n";
        echo "剩余份额: " . number_format($this->remainingShares+$buyRecordsShares, 4) . "股\n";
        echo "剩余金额: " . number_format($remainingCost, 2) . "元\n";
        echo "累计盈利: ". number_format($total_profit, 2). "元\n";
        echo "收益率: ". number_format($total_profit / ($this->remainingCost * $this->remainingShares + $buyRecordsInvestment) * 100, 2). "%\n";   

    }
    /**
     * 保存结果到Excel文件
     * @param string $outputPath 输出文件路径
     */
    private function saveToExcel($outputPath)
    {       
        // 保存文件
        $writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($this->spreadsheet);
        $writer->save($outputPath);
    }
}

// 使用示例
// $investment = new ETFInvestment('512170.xlsx',0.651, 0.269, 200);
// $investment = new ETFInvestment('159977.xlsx', 3.53, 1.571, 200);
$investment = new ETFInvestment('159770.xlsx', 1.063, 0.556, 200);
$investment->processExcelFile();
